package com.sky.sd.common.advice;

import com.sky.sd.common.exception.BaseException;
import com.sky.sd.common.exception.ControllerException;
import com.sky.sd.common.exception.ServiceException;
import com.sky.sd.common.web.response.Result;
import com.sky.sd.common.web.response.ResultType;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.ConversionNotSupportedException;
import org.springframework.beans.TypeMismatchException;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.AuthenticationTrustResolver;
import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingPathVariableException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.multipart.support.MissingServletRequestPartException;
import org.springframework.web.servlet.NoHandlerFoundException;

import java.io.IOException;
import java.util.List;

/**
 * <br/>功能: 使用需要手动注入
 * <br/>版本: 1.0
 * <br/>开发人员: 弓振
 * <br/>创建日期: 2020/3/9 17:19
 * <br/>修改日期: 2020/3/9 17:19
 * <br/>修改列表:
 */
@Slf4j
public abstract class BaseExceptionHandlerAdvice {

    private AuthenticationTrustResolver authenticationTrustResolver = new AuthenticationTrustResolverImpl();

    /**
     * 剩余异常不能捕获的错误，统一返回500
     * @param throwable
     * @return
     */
    @ExceptionHandler(value = Throwable.class)
    public ResponseEntity<Result> handlerThrowable(Throwable throwable) {
        log.error("==> Throwable 异常",throwable);
        return new ResponseEntity<Result>(Result.fail(ResultType.INTERNAL_SERVER_ERROR), HttpStatus.INTERNAL_SERVER_ERROR);
    }

    /**
     * 业务异常
     * @param baseException
     * @return
     */
    @ExceptionHandler(value = {BaseException.class, ServiceException.class, ControllerException.class})
    public ResponseEntity<Result> handlerBaseException(BaseException baseException) {
        log.error("==> BaseException 异常",baseException);
        return new ResponseEntity<Result>(Result.fail(baseException.getMessage()),HttpStatus.INTERNAL_SERVER_ERROR);
    }

    /**
     * 捕获自定义抛出来的异常信息
     * @param exception
     * @return
     */
    @ExceptionHandler(value = Exception.class)
    public ResponseEntity handlerException(Exception exception) {
        log.error("系统异常",exception);
        try {
            if (exception instanceof HttpRequestMethodNotSupportedException) {
                return handleHttpRequestMethodNotSupported((HttpRequestMethodNotSupportedException) exception);
            } else if (exception instanceof HttpMediaTypeNotSupportedException) {
                return handleHttpMediaTypeNotSupported(
                        (HttpMediaTypeNotSupportedException) exception);
            } else if (exception instanceof HttpMediaTypeNotAcceptableException) {
                return handleHttpMediaTypeNotAcceptable(
                        (HttpMediaTypeNotAcceptableException) exception);
            } else if (exception instanceof MissingPathVariableException) {
                return handleMissingPathVariable(
                        (MissingPathVariableException) exception);
            } else if (exception instanceof MissingServletRequestParameterException) {
                return handleMissingServletRequestParameter(
                        (MissingServletRequestParameterException) exception);
            } else if (exception instanceof ServletRequestBindingException) {
                return handleServletRequestBindingException(
                        (ServletRequestBindingException) exception);
            } else if (exception instanceof ConversionNotSupportedException) {
                return handleConversionNotSupported(
                        (ConversionNotSupportedException) exception);
            } else if (exception instanceof TypeMismatchException) {
                return handleTypeMismatch(
                        (TypeMismatchException) exception);
            } else if (exception instanceof HttpMessageNotReadableException) {
                return handleHttpMessageNotReadable(
                        (HttpMessageNotReadableException) exception);
            } else if (exception instanceof HttpMessageNotWritableException) {
                return handleHttpMessageNotWritable(
                        (HttpMessageNotWritableException) exception);
            } else if (exception instanceof MethodArgumentNotValidException) {
                return handleMethodArgumentNotValidException(
                        (MethodArgumentNotValidException) exception);
            } else if (exception instanceof MissingServletRequestPartException) {
                return handleMissingServletRequestPartException(
                        (MissingServletRequestPartException) exception);
            } else if (exception instanceof BindException) {
                handleBindException((BindException) exception);
            } else if (exception instanceof NoHandlerFoundException) {
                return handleNoHandlerFoundException(
                        (NoHandlerFoundException) exception);
            } else {
                log.error("系统异常",exception);
                return ResponseEntity.status(ResultType.INTERNAL_SERVER_ERROR.getCode()).body(Result.fail(ResultType.INTERNAL_SERVER_ERROR));
            }
        } catch (Exception handlerEx) {
            log.warn("尝试解决异常时失败 [" + exception.getClass().getName() + "]", handlerEx);
        }
        return ResponseEntity.status(ResultType.INTERNAL_SERVER_ERROR.getCode()).body(Result.fail(ResultType.INTERNAL_SERVER_ERROR));
    }


    /**
     *  当开启security方法类上权限控制，此时没有权限或没有认证会抛出异常，统一捕获统一响应
     * @param throwable
     * @return
     */
    @ExceptionHandler(value={AuthenticationException.class, AccessDeniedException.class})
    public ResponseEntity<Result> handlerSecurityException(RuntimeException exception) {
        Result result = Result.fail(ResultType.UNAUTHORIZED);
        if (exception instanceof AuthenticationException) {
            log.error("==> 没有认证", exception);
            result = Result.fail(ResultType.UNAUTHORIZED);
        } else {
            Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
            if (authenticationTrustResolver.isAnonymous(authentication) || authenticationTrustResolver.isRememberMe(authentication)) {
                log.error("==> 没有认证", exception);
                result = Result.fail(ResultType.UNAUTHORIZED);
            } else {
                log.error("==> 没有权限", exception);
                result = Result.fail(ResultType.FORBIDDEN);
            }
        }

        return new ResponseEntity<Result>(result, HttpStatus.valueOf(result.getCode()));
    }

    protected ResponseEntity handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException ex) throws IOException {

        String[] supportedMethods = ex.getSupportedMethods();
        MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
        if (supportedMethods != null) {
            headers.add("Allow", StringUtils.arrayToDelimitedString(supportedMethods, ", "));
        }
        return new ResponseEntity(Result.fail(ResultType.METHOD_NOT_ALLOWED), HttpStatus.valueOf(ResultType.METHOD_NOT_ALLOWED.getCode()));
    }

    protected ResponseEntity handleHttpMediaTypeNotSupported(HttpMediaTypeNotSupportedException ex) throws IOException {

        List<MediaType> mediaTypes = ex.getSupportedMediaTypes();
        MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
        if (!CollectionUtils.isEmpty(mediaTypes)) {
            headers.add("Accept", MediaType.toString(mediaTypes));
        }
        return new ResponseEntity(Result.fail(ResultType.UNSUPPORTED_MEDIA_TYPE), HttpStatus.valueOf(ResultType.UNSUPPORTED_MEDIA_TYPE.getCode()));
    }

    protected ResponseEntity handleHttpMediaTypeNotAcceptable(HttpMediaTypeNotAcceptableException ex) throws IOException {
        return new ResponseEntity(Result.fail(ResultType.NOT_ACCEPTABLE), HttpStatus.valueOf(ResultType.NOT_ACCEPTABLE.getCode()));
    }

    protected ResponseEntity handleMissingPathVariable(MissingPathVariableException ex) throws IOException {
        return new ResponseEntity(Result.fail(ResultType.INTERNAL_SERVER_ERROR), HttpStatus.valueOf(ResultType.INTERNAL_SERVER_ERROR.getCode()));
    }

    protected ResponseEntity handleMissingServletRequestParameter(MissingServletRequestParameterException ex) throws IOException {
        return new ResponseEntity(Result.fail(ResultType.INTERNAL_SERVER_ERROR), HttpStatus.valueOf(ResultType.INTERNAL_SERVER_ERROR.getCode()));
    }

    protected ResponseEntity handleServletRequestBindingException(ServletRequestBindingException ex) throws IOException {
        return new ResponseEntity(Result.fail(ResultType.PARAM_BIND_ERROR), HttpStatus.valueOf(ResultType.PARAM_BIND_ERROR.getCode()));
    }

    protected ResponseEntity handleConversionNotSupported(ConversionNotSupportedException ex) throws IOException {
        return new ResponseEntity(Result.fail(ResultType.UNSUPPORTED_MEDIA_TYPE), HttpStatus.valueOf(ResultType.INTERNAL_SERVER_ERROR.getCode()));
    }

    protected ResponseEntity handleTypeMismatch(TypeMismatchException ex) throws IOException {
        return new ResponseEntity(Result.fail(ResultType.PARAM_TYPE_ERROR), HttpStatus.valueOf(ResultType.PARAM_TYPE_ERROR.getCode()));
    }

    protected ResponseEntity handleHttpMessageNotReadable(HttpMessageNotReadableException ex) throws IOException {
        return new ResponseEntity(Result.fail(ResultType.MSG_NOT_READABLE), HttpStatus.valueOf(ResultType.MSG_NOT_READABLE.getCode()));
    }

    protected ResponseEntity handleHttpMessageNotWritable(HttpMessageNotWritableException ex) throws IOException {
        return new ResponseEntity(Result.fail(ResultType.INTERNAL_SERVER_ERROR), HttpStatus.valueOf(ResultType.INTERNAL_SERVER_ERROR.getCode()));
    }

    protected ResponseEntity handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) throws IOException {
        List<FieldError> fieldErrors = ex.getBindingResult().getFieldErrors();
        for (FieldError error : fieldErrors) {
            log.error(error.getField() + ":" + error.getDefaultMessage(), ex);
        }
        FieldError firstError = fieldErrors.get(0);
        String tt = firstError.getField();
        String tt1 = firstError.getDefaultMessage();
        String message = "";
        if(firstError.isBindingFailure()){
            message = "参数类型不匹配";
        } else {
            message = firstError.getDefaultMessage();
        }
        return new ResponseEntity(Result.fail(ResultType.PARAM_VALID_ERROR), HttpStatus.valueOf(ResultType.PARAM_VALID_ERROR.getCode()));
    }

    protected ResponseEntity handleMissingServletRequestPartException(MissingServletRequestPartException ex) throws IOException {
        return new ResponseEntity(Result.fail(ResultType.PARAM_MISS), HttpStatus.valueOf(ResultType.PARAM_MISS.getCode()));
    }

    protected ResponseEntity handleBindException(BindException ex) throws IOException {
        log.error("参数绑定异常",ex);
        List<FieldError> fieldErrors = ex.getBindingResult().getFieldErrors();
        for (FieldError error : fieldErrors) {
            log.error(error.getField() + ":" + error.getDefaultMessage(), ex);
        }
        FieldError firstError = fieldErrors.get(0);
        String tt = firstError.getField();
        String tt1 = firstError.getDefaultMessage();
        String message = "";
        if(firstError.isBindingFailure()){
            message = "参数类型不匹配";
        } else {
            message = firstError.getDefaultMessage();
        }
        return new ResponseEntity(Result.fail(ResultType.PARAM_BIND_ERROR), HttpStatus.valueOf(ResultType.PARAM_BIND_ERROR.getCode()));
    }

    protected ResponseEntity handleNoHandlerFoundException(NoHandlerFoundException ex) throws IOException {
        return new ResponseEntity(Result.fail(ResultType.NOT_FOUND), HttpStatus.valueOf(ResultType.NOT_FOUND.getCode()));
    }
}
